/*
MIT License

Copyright (c) 2018 Meng Rao <raomeng1@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#pragma once
#include <string>
#include <array>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include "tcpshm_conn.h"

namespace tcpshm {

/**
 * @brief TCP/SHM客户端模板类
 * 
 * @tparam Derived 派生类类型（CRTP模式）
 * @tparam Conf 配置类，包含各种配置参数
 */
template<class Derived, class Conf>
class TcpShmClient
{
public:
    /// 连接类型别名
    using Connection = TcpShmConnection<Conf>;
    /// 登录消息类型别名
    using LoginMsg = LoginMsgTpl<Conf>;
    /// 登录响应消息类型别名
    using LoginRspMsg = LoginRspMsgTpl<Conf>;

protected:
    /**
     * @brief 构造函数
     * @param client_name 客户端名称
     * @param ptcp_dir PTCP文件存储目录
     */
    TcpShmClient(const std::string& client_name, const std::string& ptcp_dir)
        : ptcp_dir_(ptcp_dir) {
        // 复制客户端名称，确保不超过NameSize限制
        strncpy(client_name_, client_name.c_str(), sizeof(client_name_) - 1);
        // 创建PTCP目录（如果不存在）
        mkdir(ptcp_dir_.c_str(), 0755);
        client_name_[sizeof(client_name_) - 1] = 0;
        // 初始化连接对象
        conn_.init(ptcp_dir.c_str(), client_name_);
    }

    /**
     * @brief 析构函数，停止连接并释放资源
     */
    ~TcpShmClient() {
        Stop();
    }

    /**
     * @brief 连接到服务器
     * 
     * @param use_shm 是否使用共享内存通信模式
     * @param server_ipv4 服务器IPv4地址
     * @param server_port 服务器端口
     * @param login_user_data 用户自定义登录数据
     * @return true 连接成功
     * @return false 连接失败
     */
    bool Connect(bool use_shm,
                 const char* server_ipv4,
                 uint16_t server_port,
                 const typename Conf::LoginUserData& login_user_data) {
        // 检查当前连接是否已经建立
        if(!conn_.IsClosed()) {
            static_cast<Derived*>(this)->OnSystemError("already connected", 0);
            return false;
        }
        
        // 尝试关闭之前的文件描述符
        conn_.TryCloseFd();
        const char* error_msg;
        
        // 如果还没有加载服务器名称文件，则加载
        if(!server_name_) {
            // 构造服务器名称文件路径
            std::string last_server_name_file = std::string(ptcp_dir_) + "/" + client_name_ + ".lastserver";
            // 映射服务器名称文件到内存
            server_name_ = (char*)my_mmap<ServerName>(last_server_name_file.c_str(), false, &error_msg);
            if(!server_name_) {
                static_cast<Derived*>(this)->OnSystemError(error_msg, errno);
                return false;
            }
            // 设置远程名称
            strncpy(conn_.GetRemoteName(), server_name_, sizeof(ServerName));
        }
        
        // 构造登录请求消息缓冲区
        MsgHeader sendbuf[1 + (sizeof(LoginMsg) + 7) / 8];
        sendbuf[0].size = sizeof(MsgHeader) + sizeof(LoginMsg);  // 消息大小
        sendbuf[0].msg_type = LoginMsg::msg_type;               // 消息类型
        sendbuf[0].ack_seq = 0;                                 // 确认序号初始为0
        LoginMsg* login = (LoginMsg*)(sendbuf + 1);             // 登录消息体指针
        
        // 填充登录消息内容
        strncpy(login->client_name, client_name_, sizeof(login->client_name));           // 客户端名称
        strncpy(login->last_server_name, server_name_, sizeof(login->last_server_name)); // 上次服务器名称
        login->use_shm = use_shm;                                                        // 是否使用SHM
        login->client_seq_start = login->client_seq_end = 0;                             // 客户端序列号范围
        login->user_data = login_user_data;                                              // 用户自定义数据
        
        // 如果上次服务器名称存在，则尝试打开相关文件并获取序列号
        if(server_name_[0] &&
           (!conn_.OpenFile(use_shm, &error_msg) ||
            !conn_.GetSeq(&sendbuf[0].ack_seq, &login->client_seq_start, &login->client_seq_end, &error_msg))) {
            static_cast<Derived*>(this)->OnSystemError(error_msg, errno);
            return false;
        }
        
        // 创建TCP套接字
        int fd;
        if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            static_cast<Derived*>(this)->OnSystemError("socket", errno);
            return false;
        }
        
        // 设置套接字接收和发送超时时间
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;

        if(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
            static_cast<Derived*>(this)->OnSystemError("setsockopt SO_RCVTIMEO", errno);
            close(fd);
            return false;
        }

        if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
            static_cast<Derived*>(this)->OnSystemError("setsockopt SO_RCVTIMEO", errno);
            close(fd);
            return false;
        }
        
        // 根据配置决定是否启用TCP_NODELAY选项
        int yes = 1;
        if(Conf::TcpNoDelay && setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) < 0) {
            static_cast<Derived*>(this)->OnSystemError("setsockopt TCP_NODELAY", errno);
            close(fd);
            return false;
        }

        // 构造服务器地址结构
        struct sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;
        inet_pton(AF_INET, server_ipv4, &(server_addr.sin_addr));  // 设置服务器IP地址
        server_addr.sin_port = htons(server_port);                 // 设置服务器端口
        bzero(&(server_addr.sin_zero), 8);                         // 清零保留字段

        // 连接服务器
        if(connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            static_cast<Derived*>(this)->OnSystemError("connect", errno);
            close(fd);
            return false;
        }

        // 转换字节序（根据配置）
        sendbuf[0].template ConvertByteOrder<Conf::ToLittleEndian>();
        login->ConvertByteOrder();
        
        // 发送登录请求消息
        int ret = send(fd, sendbuf, sizeof(sendbuf), MSG_NOSIGNAL);
        if(ret != sizeof(sendbuf)) {
            static_cast<Derived*>(this)->OnSystemError("send", ret < 0 ? errno : 0);
            close(fd);
            return false;
        }

        // 接收登录响应消息
        MsgHeader recvbuf[1 + (sizeof(LoginRspMsg) + 7) / 8];
        ret = recv(fd, recvbuf, sizeof(recvbuf), 0);
        if(ret != sizeof(recvbuf)) {
            static_cast<Derived*>(this)->OnSystemError("recv", ret < 0 ? errno : 0);
            close(fd);
            return false;
        }
        
        // 解析登录响应消息
        LoginRspMsg* login_rsp = (LoginRspMsg*)(recvbuf + 1);
        recvbuf[0].template ConvertByteOrder<Conf::ToLittleEndian>();
        login_rsp->ConvertByteOrder();
        
        // 验证响应消息格式是否正确
        if(recvbuf[0].size != sizeof(MsgHeader) + sizeof(LoginRspMsg) || 
           recvbuf[0].msg_type != LoginRspMsg::msg_type ||
           login_rsp->server_name[0] == 0) {
            static_cast<Derived*>(this)->OnSystemError("Invalid LoginRsp", 0);
            close(fd);
            return false;
        }
        
        // 检查登录状态
        if(login_rsp->status != 0) {
            if(login_rsp->status == 1) { // 序列号不匹配
                // 恢复发送缓冲区字节序
                sendbuf[0].template ConvertByteOrder<Conf::ToLittleEndian>();
                login->ConvertByteOrder();
                // 调用序列号不匹配回调
                static_cast<Derived*>(this)->OnSeqNumberMismatch(sendbuf[0].ack_seq,
                                                                 login->client_seq_start,
                                                                 login->client_seq_end,
                                                                 recvbuf[0].ack_seq,
                                                                 login_rsp->server_seq_start,
                                                                 login_rsp->server_seq_end);
            }
            else {
                // 调用登录拒绝回调
                static_cast<Derived*>(this)->OnLoginReject(login_rsp);
            }
            close(fd);
            return false;
        }
        
        // 确保服务器名称字符串以'\0'结尾
        login_rsp->server_name[sizeof(login_rsp->server_name) - 1] = 0;
        
        // 检查服务器名称是否发生变化
        if(strncmp(server_name_, login_rsp->server_name, sizeof(ServerName)) != 0) {
            conn_.Release();  // 释放旧连接
            // 更新服务器名称
            strncpy(server_name_, login_rsp->server_name, sizeof(ServerName));
            strncpy(conn_.GetRemoteName(), server_name_, sizeof(ServerName));
            
            // 重新打开新的连接文件
            if(!conn_.OpenFile(use_shm, &error_msg)) {
                static_cast<Derived*>(this)->OnSystemError(error_msg, errno);
                close(fd);
                return false;
            }
            conn_.Reset();  // 重置连接
        }
        
        // 设置套接字为非阻塞模式
        fcntl(fd, F_SETFL, O_NONBLOCK);
        
        // 调用登录成功回调，获取当前时间戳
        int64_t now = static_cast<Derived*>(this)->OnLoginSuccess(login_rsp);

        // 正式打开连接
        conn_.Open(fd, recvbuf[0].ack_seq, now);
        return true;
    }

    /**
     * @brief 轮询TCP消息（即使使用SHM也需要轮询TCP）
     * @param now 当前时间戳
     */
    void PollTcp(int64_t now) {
        // 如果连接未关闭，则检查是否有TCP消息到达
        if(!conn_.IsClosed()) {
            MsgHeader* head = conn_.TcpFront(now);
            if(head) static_cast<Derived*>(this)->OnServerMsg(head);
        }
        
        // 尝试关闭文件描述符，检查连接是否已断开
        if(conn_.TryCloseFd()) {
            int sys_errno;
            const char* reason = conn_.GetCloseReason(&sys_errno);
            static_cast<Derived*>(this)->OnDisconnected(reason, sys_errno);
        }
    }

    /**
     * @brief 轮询SHM消息（仅在使用SHM时调用）
     */
    void PollShm() {
        MsgHeader* head = conn_.ShmFront();
        if(head) static_cast<Derived*>(this)->OnServerMsg(head);
    }

    /**
     * @brief 停止连接并关闭文件
     */
    void Stop() {
        if(server_name_) {
            my_munmap<ServerName>(server_name_);  // 取消内存映射
            server_name_ = nullptr;
        }
        conn_.Release();  // 释放连接资源
    }

    /**
     * @brief 获取连接引用
     * @return Connection& 连接对象引用
     */
    Connection& GetConnection() {
        return conn_;
    }

private:
    char client_name_[Conf::NameSize];       // 客户端名称
    using ServerName = std::array<char, Conf::NameSize>;  // 服务器名称类型
    char* server_name_ = nullptr;            // 服务器名称指针
    std::string ptcp_dir_;                   // PTCP文件目录
    Connection conn_;                        // 连接对象
};
} // namespace tcpshm